package com.zzyl.nursing.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zzyl.common.constant.CacheConstants;
import com.zzyl.common.utils.StringUtils;
import com.zzyl.nursing.config.WebSocketServer;
import com.zzyl.nursing.domain.AlertData;
import com.zzyl.nursing.domain.AlertRule;
import com.zzyl.nursing.domain.DeviceData;
import com.zzyl.nursing.mapper.AlertRuleMapper;
import com.zzyl.nursing.mapper.DeviceMapper;
import com.zzyl.nursing.service.IAlertDataService;
import com.zzyl.nursing.service.IAlertRuleService;
import com.zzyl.nursing.vo.AlertNotifyVo;
import com.zzyl.system.mapper.SysUserRoleMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 报警规则Service业务层处理
 *
 * @author cao
 * @date 2025-01-07
 */
@Slf4j
@Service
public class AlertRuleServiceImpl extends ServiceImpl<AlertRuleMapper, AlertRule> implements IAlertRuleService {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private DeviceMapper deviceMapper;

    @Autowired
    private SysUserRoleMapper sysUserRoleMapper;
    @Autowired
    private IAlertDataService alertDataService;

    @Value("${alert.deviceMaintainerRole}")
    private String deviceMaintainerRole;

    @Value("${alert.managerRole}")
    private String managerRole;

    /**
     * 查询报警规则列表
     *
     * @param alertRule 报警规则
     * @return 报警规则
     */
    @Override
    public List<AlertRule> selectAlertRuleList(AlertRule alertRule) {
        return lambdaQuery()
            .eq(StringUtils.isNotEmpty(alertRule.getProductKey())
, AlertRule::getProductKey, alertRule.getProductKey())
            .like(StringUtils.isNotEmpty(alertRule.getProductName()), AlertRule::getProductName, alertRule.getProductName())
            .eq(StringUtils.isNotEmpty(alertRule.getModuleId())
, AlertRule::getModuleId, alertRule.getModuleId())
            .like(StringUtils.isNotEmpty(alertRule.getModuleName()), AlertRule::getModuleName, alertRule.getModuleName())
            .like(StringUtils.isNotEmpty(alertRule.getFunctionName()), AlertRule::getFunctionName, alertRule.getFunctionName())
            .eq(StringUtils.isNotEmpty(alertRule.getFunctionId())
, AlertRule::getFunctionId, alertRule.getFunctionId())
            .eq(StringUtils.isNotEmpty(alertRule.getIotId())
, AlertRule::getIotId, alertRule.getIotId())
            .like(StringUtils.isNotEmpty(alertRule.getDeviceName()), AlertRule::getDeviceName, alertRule.getDeviceName())
            .eq(Objects.nonNull(alertRule.getAlertDataType()), AlertRule::getAlertDataType, alertRule.getAlertDataType())
            .like(StringUtils.isNotEmpty(alertRule.getAlertRuleName()), AlertRule::getAlertRuleName, alertRule.getAlertRuleName())
            .eq(StringUtils.isNotEmpty(alertRule.getOperator())
, AlertRule::getOperator, alertRule.getOperator())
            .eq(Objects.nonNull(alertRule.getValue()), AlertRule::getValue, alertRule.getValue())
            .eq(Objects.nonNull(alertRule.getStatus()), AlertRule::getStatus, alertRule.getStatus())
            .list();
    }

    @Override
    public void alertFilter() {
        Long count = lambdaQuery().eq(AlertRule::getStatus, 1).count();
        // 没有规则则结束
        if (count == 0) {
            log.info("没有有效的规则.");
            return;
        }

        //2.查询所有设备上报的数据, 怎么放进去key，怎么取，key
        //redisTemplate.opsForHash().put(CacheConstants.IOT_DEVICEDATA_LATEST, content.getIotId(), JSONUtil.toJsonStr(list));
        List<Object> allDataList = redisTemplate.opsForHash().values(CacheConstants.IOT_DEVICEDATA_LATEST);
        if(CollUtil.isEmpty(allDataList)){
            // 没有上报的数据则结束
            log.info("没有上报的设备数据则结束");
            return;
        }

        //3.解析设备上报的每一条数据到新的List中
        List<DeviceData> deviceDataList = new ArrayList<>();
        allDataList.forEach(data->{
            // 上报一次时的数据
            List<DeviceData> dataList = JSONUtil.toList( data.toString(), DeviceData.class);
            deviceDataList.addAll(dataList);
        });
        //4.遍历数据，交给数据报警处理器
        deviceDataList.forEach(this::alertFilter);
    }

    /**
     * 报警数据过滤处理
     *
     * @param deviceData
     */
    private void alertFilter(DeviceData deviceData) {
        //准备对应报警规则数据
        List<AlertRule> allRulesList = new ArrayList<>();
        // 针对一个产品下的所有设备规则
        List<AlertRule> productRuleList = getRulesByProductKey(deviceData.getProductKey(), deviceData.getFunctionId());
        // 只针对一个设备的规则
        List<AlertRule> deviceRulesList = getRulesByIotId(deviceData.getIotId(), deviceData.getFunctionId());
        // 合并
        if (CollUtil.isNotEmpty(productRuleList)) {
            allRulesList.addAll(productRuleList);
        }
        if (CollUtil.isNotEmpty(deviceRulesList)) {
            allRulesList.addAll(deviceRulesList);
        }
        // 按照过滤规则和上报的数据 遍历每一条规则进行匹配
        allRulesList.forEach(alertRule -> deviceDataAlarmHandler(alertRule, deviceData));
    }

    /**
     * 对每个规则进行过滤处理
     * @param alertRule
     * @param deviceData
     */
    private void deviceDataAlarmHandler(AlertRule alertRule, DeviceData deviceData) {
        //第一层校验： 判断生效时间
        // 判断是否在生效时间内, 上报数据的时间是否在规则的生效时间段内
        // 上报数据的时间
        LocalTime alarmTime = deviceData.getAlarmTime().toLocalTime();
        //规则的生效时间段内 00:00:00~23:59:59, 段就有开始，有结束
        String alertEffectivePeriod = alertRule.getAlertEffectivePeriod();
        String[] split = alertEffectivePeriod.split("~");
        LocalTime start = LocalTime.parse(split[0]);
        LocalTime end = LocalTime.parse(split[1]);
        // 比开始大，比结束小
        if (alarmTime.isBefore(start) || alarmTime.isAfter(end)) {
            //不在生效时间段内
            return;
        }

        //第二层校验：判断数据阈值,谁跟谁比较，上报的数据与规则设置的阈值
        //规则里设置的阈值
        Double value = alertRule.getValue();
        //上报的数据
        String dataValueString = deviceData.getDataValue();
        Double dataValue = Double.valueOf(dataValueString);
        //检验数据是否达到阈值, p1>p2则返回1，p1<p2返回-1，p1=p2返回0
        int compareResult = NumberUtil.compare(dataValue, value);
        // 怎么比较，规则里有操作符   55<65
        String operator = alertRule.getOperator();

        String iotId = deviceData.getIotId();
        // 统计次数的key
        String aggCountKey = CacheConstants.ALERT_TRIGGER_COUNT_PREFIX + iotId + ":" + deviceData.getFunctionId() + ":" + alertRule.getId();
        //规则的要求，上报的数据要>=阈值但实际上报的数据值小于阈值  规则的要求: 上报的数据<阈值,实际上报的数据值大于阈值
        //1.上报的数据 55， 阈值是65， operation <
        // compareResult=-1  compareResult<0报警 >=0不用报警
        //2. 上报的数据 55， 阈值是65， operation >=
        if(("<".equals(operator) && compareResult>=0) || (">=".equals(operator) && compareResult < 0)){
            //不用报警
            // 累计的次数清零
            redisTemplate.delete(aggCountKey);
            return;
        }

        //第三层校验： 判断沉默周期
        // 是否在沉默周期内, 如果redis中不存在key说明 沉默周期已结束
        String silentKey = CacheConstants.ALERT_SILENT_PREFIX + iotId + ":" + deviceData.getFunctionId() + ":" + alertRule.getId();
        if (redisTemplate.hasKey(silentKey)) {
            // 在沉默周期内
            return;
        }

        //第四层校验：判断持续周期，累计次数>=持续周期 就要报警，
        // 不在沉默周期内，且数据满足报警阈值，计数器+1
        // 先+1，返回最新的值, +1 + 1 + 1
        Long increment = redisTemplate.opsForValue().increment(aggCountKey);
        // 持续周期在哪
        Integer duration = alertRule.getDuration();
        if(NumberUtil.compare(increment, duration)<0){
            //没达到持续周期
            return;
        }
        // 要报警了
        // 删除redis报警数，下次要重新计数
        redisTemplate.delete(aggCountKey);
        // 设置沉默周期，并指定key的有效期为沉默周期的时间
        redisTemplate.opsForValue().set(silentKey, "1",alertRule.getAlertSilentPeriod(), TimeUnit.MINUTES);

        //添加报警数据，查询通知的人员
        Set<Long> userIds = new HashSet<Long>();
        //是否为老人数据 报警数据类型
        if (ObjectUtil.equal(alertRule.getAlertDataType(),0)) {
            // 0是老人数据
            // 判断是否是固定设备 location_type
            if (ObjectUtil.equal(deviceData.getLocationType(),0)) {
                // 随身设备
                userIds.addAll(deviceMapper.selectNursingIdsByIotIdWithElder(iotId));
            }else{
                // 床位
                userIds.addAll(deviceMapper.selectNursingIdsByIotIdWithBed(iotId));
            }
        }else{
            //设备数据, 找维修工
            userIds.addAll(sysUserRoleMapper.selectUserIdByRoleName(deviceMaintainerRole));
        }
        // 无论哪种报警都要通知超级管理员
        userIds.addAll(sysUserRoleMapper.selectUserIdByRoleName(managerRole));

        // 批量添加报警数据
        List<AlertData> alertData = insertAlertData(userIds, alertRule, deviceData);
        // websocket推送消息
        websocketNotity(alertData.get(0), alertRule, userIds);


    }


    @Autowired
    private WebSocketServer webSocketServer;

    /**
     * websocket推送消息
     * @param alertData
     * @param rule
     * @param allUserIds
     */
    private void websocketNotity(AlertData alertData, AlertRule rule, Collection<Long> allUserIds) {

        // 属性拷贝
        AlertNotifyVo alertNotifyVo = BeanUtil.toBean(alertData, AlertNotifyVo.class);
        alertNotifyVo.setFunctionName(rule.getFunctionName());
        alertNotifyVo.setAlertDataType(rule.getAlertDataType());
        alertNotifyVo.setNotifyType(1);
        // 向指定的人推送消息
        webSocketServer.sendMessageToConsumer(alertNotifyVo, allUserIds);

    }


    /**
     * 保存报警数据
     *
     * @param allUserIds
     * @param rule
     * @param deviceData
     */
    private List<AlertData> insertAlertData(Collection<Long> allUserIds, AlertRule rule, DeviceData deviceData) {
        // 对象拷贝
        AlertData alertData = BeanUtil.toBean(deviceData, AlertData.class);
        alertData.setAlertRuleId(rule.getId());
        // 心率<60,持续3个周期就报警
        String alertReason = StrUtil.format("{}{}{},持续{}个周期就报警", rule.getFunctionName(), rule.getOperator(), rule.getValue(), rule.getDuration());
        alertData.setAlertReason(alertReason);
        alertData.setStatus(0);//没处理
        alertData.setType(rule.getAlertDataType());
        // 遍历allUserIds
        List<AlertData> list = allUserIds.stream().map(userId -> {
            // 以alertData为模板创建新的对象
            AlertData dbAlertData = BeanUtil.toBean(alertData, AlertData.class);
            dbAlertData.setUserId(userId);
            dbAlertData.setId(null);
            return dbAlertData;
        }).collect(Collectors.toList());

        // 批量保存
        alertDataService.saveBatch(list);
        return list;
    }

    /**
     * 获取一个设备下的所有规则
     *
     * @param iotId
     * @param functionId
     * @return
     */
    private List<AlertRule> getRulesByIotId(String iotId, String functionId) {
        return lambdaQuery().eq(AlertRule::getIotId, iotId)
                .eq(AlertRule::getFunctionId, functionId)
                .eq(AlertRule::getStatus, 1).list();
    }

    /**
     * 查询一个产品下的所有设备规则
     *
     * @param productKey
     * @param functionId
     * @return
     */
    private List<AlertRule> getRulesByProductKey(String productKey, String functionId) {
        return lambdaQuery().eq(AlertRule::getProductKey, productKey)
                .eq(AlertRule::getFunctionId, functionId)
                // 查询产品下的所有的规则，要排除掉设备的规则，否则当有设备规则时，这条数据也满足这个产品下的所有规则，导致这个规则被查询了2次
                .eq(AlertRule::getIotId, "-1")
                .eq(AlertRule::getStatus, 1).list();
    }
}

